package com.link.gateway.core.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Splitter;
import com.link.gateway.base.auth.model.Authclient;
import com.link.gateway.base.auth.service.AuthclientService;
import com.link.gateway.base.enterprise.model.Enterprise;
import com.link.gateway.base.enterprise.service.EnterpriseService;
import com.link.gateway.base.user.model.User;
import com.link.gateway.base.user.service.UserService;
import com.link.gateway.core.comproperty.RunProperties;
import com.link.gateway.core.comproperty.TokenConstants;
import com.link.gateway.core.exception.AuthFailedException;
import com.link.gateway.core.exception.IllegalStrException;
import com.link.gateway.microservice.MicroAuthService;
import com.link.gateway.core.redis.redisclient.LinkRedisClient;
import feign.FeignException;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.ExecutionException;

/**
 * token工具：校验token、生成内部token
 */
@Component
public class TokenUtils {

    private static final Logger logger = LogManager.getLogger(TokenUtils.class);

    private TokenUtils() {
    }

    /**
     * 20分钟
     */
    private static final int MIN = 20 * 60 * 1000;

    private static final String CLIENT_ID = "clientId";

    /**
     * @auther  guoshan
     * @data  2019-07-18
     * 根据不同的请求类型生成内部token；如果请求头是Authorization认证方式，需要判断scope是否是API，如果是需要校验IP黑白名单
     *  如果是api 公钥的方式则根据clientId 查找用户信息，企业信息，然后加密生成内部token；
     *  如果是非api公钥的请求类型，则从Redis中获取用户信息
     * @param token
     * @param type 请求类型
     * @param url 当前请求的url
     * @param reqIp 请求ip
     * @return
     */
    public static Map<String, Object> genInnerToken(String token, String type, String reqIp,String url) throws Exception {
        if (!TokenConstants.AUTH_HEADER_NAME.equals(type) && !TokenConstants.API_AUTH_HEADER_NAME.equals(type)) {
            throw new IllegalArgumentException("请求头信息错误");
        }

        Map<String, Object> modelMap = new HashMap<>(8);
        if (TokenConstants.API_AUTH_HEADER_NAME.equals(type)) {
            //如果是api 公钥加密的方式
            Map<String, String> result = parseToken(token);
            String clientId = result.get(CLIENT_ID);
            String userName = result.get("username");
            User user;
            if (StringUtils.isNotBlank(userName)) {
                user = SpringUtils.getBean(UserService.class).queryByUsername(userName);
            } else {
                //根据clientId 获取用户企业信息
                user = SpringUtils.getBean(UserService.class).queryUserByClient(clientId);
            }

            user.setCurClientId(clientId);
            Enterprise enterprise = SpringUtils.getBean(EnterpriseService.class).queryByUserCorpId(user);
            modelMap.put(TokenConstants.USER_KEY, user);
            modelMap.put(TokenConstants.ENTERPRISE, enterprise);

        } else {
            if ("/action/base/user/getUserCtx".equals(url)) {
                // /base/user/getUserCtx 这个请求，首先刷新用户信息
                CacheUtil.refreshModelMapFromCache(token);
            }
            Map<String,Object>  cacheMap = CacheUtil.getModelMapFromCache(token);
            modelMap.put(TokenConstants.USER_KEY, cacheMap.get(TokenConstants.USER_KEY));
            modelMap.put(TokenConstants.ENTERPRISE, cacheMap.get(TokenConstants.ENTERPRISE));
        }

        //从map中获取user和enterprise ，用于生成内部token，并去除部分字段放入session。
        User user = (User) modelMap.get(TokenConstants.USER_KEY);
        Enterprise enterprise = (Enterprise) modelMap.get(TokenConstants.ENTERPRISE);

        //获取用户关联的api客户端
        Authclient authclient = CacheUtil.getAuthclientCache(user.getCurClientId());
        //校验黑白名单
        String whites = authclient.getWhiteList();
        String blacks = authclient.getBlackList();
        boolean result = verifyBlackWhite(reqIp, whites, blacks);
        if (!result) {
            throw new IllegalAccessException("该ip已被禁止访问");
        }


        String userEnterJson = JSON.toJSONString(modelMap);
        String innerToken = AesUtil.encrypt(userEnterJson);
        //将生成的内部token存入map中
        modelMap.put(TokenConstants.INNER_TOKEN,innerToken);

        //把用户信息和企业信息存入到session里面去，供后面使用 HuangLJ 2019年07月25日
        String userJson = JSONObject.toJSONString(user);
        user = jsonObjToUser(JSONObject.parseObject(userJson), authclient.getClientId());
        String entJson = JSONObject.toJSONString(enterprise);
        enterprise = jsonObjToEnt(JSONObject.parseObject(entJson));

    /*
        备份Map<String,Object> innTokenMap = new HashMap<>(2);
        innTokenMap.put(TokenConstants.USER_KEY,user);
        innTokenMap.put(TokenConstants.ENTERPRISE,enterprise);
        String userEnterJson = JSON.toJSONString(innTokenMap);
        String innerToken = AesUtil.encrypt(userEnterJson);
        //将生成的内部token存入map中
        modelMap.put(TokenConstants.INNER_TOKEN,innerToken);
    */


        modelMap.put(TokenConstants.USER_JSON_KEY, userJson);
        modelMap.put(TokenConstants.ENTERPRISE_JSON_KEY, entJson);
        RunProperties.TEMP_USER.set(user);
        RunProperties.TEMP_ENTERPRISE.set(enterprise);
        return modelMap;
    }

    /**
     * @author guoshan
     * @date 2019/08/01
     * @param userJson  用户信息转换成的json字符串
     * @param enterpriseJson    企业信息转换承德json字符串
     * @return
     */
    public static String genCustomToken(String userJson, String enterpriseJson) {
        String uuid = UUID.randomUUID().toString();
        String customToken =  "api_" + uuid;
        String loginInfoKey = TokenConstants.LOGIN_INFO_PREFIX  + customToken;
        String authUserKey = TokenConstants.TOKEN_PREFIX + customToken;
        LinkRedisClient jedis = null;

        try {
            jedis = RedisUtil.getJedis();
            //存储login_info
            jedis.hset(loginInfoKey,TokenConstants.ENTERPRISE,enterpriseJson);
            jedis.hset(loginInfoKey,TokenConstants.ACCESS_TOKEN_FIELD,uuid);
            //存储auth_user
            jedis.set(authUserKey,userJson);
            jedis.expire(loginInfoKey,300);
            jedis.expire(authUserKey,300);
        } finally {
            RedisUtil.returnResource(jedis);
        }
        return  customToken;
    }

    /**
     * @author guoshan
     * @data  2019-07-18
     * 从redis中获取用户信息和企业信息并存储在map中
     * @param token
     * @return
     */
    public static Map<String, Object> getModelMapFromRedis(String token) {
        LinkRedisClient jedis = null;
        List<String> list;
        String userStr;
        try {
            jedis = RedisUtil.getJedis();
            //获取用户信息，用aes加密生成内部token
            userStr = jedis.get(TokenConstants.TOKEN_PREFIX + token);
            String loginInfoKey = TokenConstants.LOGIN_INFO_PREFIX + token;
            list = jedis.hmget(loginInfoKey, TokenConstants.ENTERPRISE);
        } finally {
            RedisUtil.returnResource(jedis);
        }
        User user = JSON.parseObject(userStr,User.class);
        String enterpriseJson = list.get(0);
        Enterprise enterprise = JSON.parseObject(enterpriseJson,Enterprise.class);
        Map<String, Object> modelMap = new HashMap<>(6);
        modelMap.put(TokenConstants.USER_KEY, user);
        modelMap.put(TokenConstants.ENTERPRISE, enterprise);
        return modelMap;
    }

    /**
     * @author  guoshan
     * @data 2019-07-18
     * 校验通用token，刷新token有效时间，
     */
    public static User verifyToken(String token) throws Exception {

        /**
         * 判断是api还是通用token
         */
        if (StringUtils.isNotBlank(token)) {
            try {
                String ret = SpringUtils.getBean(MicroAuthService.class).checkToken("bearer" + token);
                JSONObject authObj = JSONObject.parseObject(ret);
                //获取得到当前session有效时间刷新当前redis中的时间
                Integer tokenExpire = authObj.getInteger("tokenExpire");
                String scope = authObj.getString("scope");
                String clientId = authObj.getString("curClientId");
                User user = new User();
                user.setCurClientId(clientId);
                user.setCurScope(scope);
                LinkRedisClient jedis = null;
                try {
                    jedis = RedisUtil.getJedis();

                    jedis.expire(TokenConstants.TOKEN_PREFIX + token, tokenExpire);
                    jedis.expire(TokenConstants.LOGIN_INFO_PREFIX + token, tokenExpire);
                } finally {
                    RedisUtil.returnResource(jedis);
                }
                return user;

            } catch (FeignException fe) {
                logger.error("authClients报错：" + fe.getMessage());
                if (HttpStatus.UNAUTHORIZED.value() == fe.status()) {
                    return null;
                } else {
                    logger.error("authClients报错：" + fe.getMessage());
                    throw new AuthFailedException("认证服务器报错，请联系管理人员，报错代码：" + fe.status());
                }
            } catch (Exception e) {
                logger.error(e.getMessage());
                throw new AuthFailedException("校验token报错，请联系管理员！");
            }
        } else {
            return null;
        }

    }

    /**
     * @author  guoshan
     * @data  2019-07-18
     * 校验IP  ； 校验api token;token格式为：base64(client_id|rsa(client_id|随机数|时间戳|用户名))
     * 用base64解码token字符串，从json字符串中获取client_id，通过clientId 获取公钥，然后校验
     */
    public static boolean verifyApiToken(String token, String url) throws Exception {
        Map<String,String> parseResult = parseToken(token);


        String clientId = parseResult.get(CLIENT_ID);
        String oriClientId = parseResult.get("oriClientId");

        String urlInput = parseResult.get("url");

        if (StringUtils.isBlank(urlInput) || !urlInput.endsWith(url)) {
            //如果传入的url不是实际调用的接口，则不予通过
            return false;
        }

        /*
         * 判断clientId是否相等，判断时间戳是否满足条件
         */
        if (!clientId.equals(oriClientId)) {
            return false;
        }

        //获取时间戳
        long timestamp = Long.parseLong(parseResult.get("timestamp"));
        long currTimeMillis = System.currentTimeMillis();
        if (currTimeMillis - timestamp > MIN  && timestamp - currTimeMillis < MIN) {
            return false;
        }
        //把范围设置到线程中去 HuangLJ 2019年07月24日
        RunProperties.SCOPE.set("api");
        RunProperties.CURRENT_CLIENT_ID.set(clientId);
        return true;
    }

    /**
     * @description  校验黑白名单；判断黑白名单是否为空，为空则返回true；不为空判断请求ip是否在黑名单中，在则返回false；不在则判断；
     * @author guoshan
     * @date   2019/08/19
     * @param reqIp,请求的ip
     * @param whites:ip白名单
     * @param blacks: ip黑名单
     * @return
     */
    public static boolean verifyBlackWhite(String reqIp, String whites, String blacks) {

        //判断是否有配置黑白名单，没有直接返回true
        if (StringUtils.isBlank(whites) && StringUtils.isBlank(blacks)) {
            return true;
        }

        if (StringUtils.isBlank(whites)) {
            //如果没有白名单，黑名单必然存在，判断是否在黑名单中，在返回false，不在返回true
            List<String> blackIpList = Splitter.on(',').trimResults().omitEmptyStrings().splitToList(blacks);
            boolean contains = blackIpList.contains(reqIp);
            if (contains) {
                return false;
            }
            return  true;
        } else {
            //白名单必然存在，判断是否在白名单中，在返回true；不在则返回false
            List<String> whiteIpList = Splitter.on(',').trimResults().omitEmptyStrings().splitToList(whites);
            if (whiteIpList.contains(reqIp)) {
                return true;
            }

            return false;
        }


    }

    /**
     * @author  guoshan
     * 在api公私钥模式下，解析出用户传递的token中的各个部分(client_id|rsa(client_id|随机数|时间戳|用户名))
     * @param token
     * @return
     */
    private static Map<String, String> parseToken(String token) throws Exception {
        Map<String,String> result = new HashMap<>(6);
        byte[] tokenBytes = Base64Utils.decode(token);
        String tokenString = new String(tokenBytes);
        //token中携带的客户端id,即前端传递的clientId
        String clientId = StringUtils.substringBefore(tokenString, "|");

        //查看第三方客户端，从中获取私钥入解析第二部分内容
        Authclient authclient = SpringUtils.getBean(AuthclientService.class).queryClientByClientId(clientId);

        if (authclient == null ) {
            throw  new NullPointerException("第三方客户端不存在");
        }
        String rsaPrivateKey = authclient.getRsaPrivateKey();
        String rasEncryptStr = StringUtils.substringAfter(tokenString, "|");

        String decryptStr = null;
        try {
            decryptStr = RSAUtils.decryptByPrivateKeyAndBase64(rasEncryptStr, rsaPrivateKey);
        } catch (Exception e) {
            throw new   IllegalArgumentException("token格式错误");
        }
        String[] arr = StringUtils.split(decryptStr, "|");
        if (arr.length != 5 && arr.length != 4) {
            throw new IllegalArgumentException("token格式错误");
        }

        result.put(CLIENT_ID, clientId);
        //存储加密字符串中的client，即(client_id|rsa(client_id|随机数|时间戳|用户名)) 中第二个client_id
        result.put("oriClientId", arr[0]);
        //存储随机数
        result.put("randNum", arr[1]);
        //时间戳
        result.put("timestamp", arr[2]);
        //url
        result.put("url", arr[3]);
        if (arr.length == 5) {
            //获取用户信息信息，可为空
            result.put("username", arr[4]);
        }
        return  result;
    }


    /**
     * @author HuangLJ
     * @date 2019-07-24 14:11
     * 截取出token的值
     */
    public static String getToken(String tokenMsg) throws IllegalStrException {
        String[] tokenArr = tokenMsg.split("\\s+");

        if (tokenArr.length != 2) {
            throw new IllegalStrException( "传入的token格式有误！");
        }
        return tokenArr[1];
    }

    /**
     * 获取redis中的登录相关信息
     *
     * @author Huanglongjian
     * @param key 参数的key
     * @param token token信息
     * @date 2018年4月14日
     */
    public static String getAttribute(String key, String token) throws NullPointerException {
        if (StringUtils.isBlank(token)) {
            throw new NullPointerException("token不能为空");
        }
        if (StringUtils.isBlank(key)) {
            throw new NullPointerException("key不能为空");
        }
        LinkRedisClient jedis = RedisUtil.getJedis();
        String loginInfoKey = TokenConstants.LOGIN_INFO_PREFIX + token;
        List<String> list = jedis.hmget(loginInfoKey, key);
        RedisUtil.returnResource(jedis);
        if (list == null || list.isEmpty()) {
            return null;
        }
        return list.get(0);
    }

    /**
     * 设置session信息到线程里面去
     *
     * @author Huanglongjian
     * @param request 请求信息
     *                把当前token中用户信息和企业信息放入到线程里面去
     * @date 2019-07-22 11:28
     */
    public static void setSessionToThread(ServerHttpRequest request) throws Exception {
        String token = getTokenFromRequest(request);
        //获取用户信息存进去
        LinkRedisClient linkRedisClient = null;
        try {
            linkRedisClient = RedisUtil.getJedis();

            //获取企业信息存进去
            String enterpriseStr = getAttribute(TokenConstants.ENTERPRISE, token);
            JSONObject enterpriseJsonObject = JSON.parseObject(enterpriseStr);
            Enterprise enterprise = jsonObjToEnt(enterpriseJsonObject);
            RunProperties.TEMP_ENTERPRISE.set(enterprise);

            //把用户信息放进去
            String userStr = linkRedisClient.get(TokenConstants.TOKEN_PREFIX.concat(token));
            JSONObject userJsonObject = JSON.parseObject(userStr);
            User user = jsonObjToUser(userJsonObject, null);
            RunProperties.TEMP_USER.set(user);
        } catch (Exception e) {
            logger.error(e.getMessage());
            throw new Exception(e);
        } finally {
            RedisUtil.returnResource(linkRedisClient);
        }

    }

    /**
     * @author Guoshan
     * @date 2019-07-22 11:58
     * 获取当前请求对应的token的头，api或者非api请求请求
     */
    public static String getTokenHeader(ServerHttpRequest request) {
        HttpHeaders reqHeaders = request.getHeaders();
        boolean auth = reqHeaders.containsKey(TokenConstants.AUTH_HEADER_NAME);
        boolean apiAuth = reqHeaders.containsKey(TokenConstants.API_AUTH_HEADER_NAME);
        if (auth) {
            return TokenConstants.AUTH_HEADER_NAME;
        } else if (apiAuth) {
            return TokenConstants.API_AUTH_HEADER_NAME;
        } else {
            return TokenConstants.UNKNOWN_REQ_TYPE;
        }
    }

    /**
     * 在请求信息中获取得到token
     *
     * @author huanglongjian
     * @date 2019-07-22 12:12
     */
    public static String getTokenFromRequest(ServerHttpRequest request) throws Exception {
        String loginHeader = getTokenHeader(request);
        HttpHeaders reqHeaders = request.getHeaders();
        String token = null;
        if (reqHeaders.containsKey(loginHeader)) {
            List<String> tokenList = reqHeaders.get(loginHeader);
            if (tokenList == null || tokenList.isEmpty()) {
                throw new NullPointerException("token信息为空！");
            }

            if (tokenList.size() > 1) {
                throw new IllegalAccessException("token信息大于1！");
            }

            String tokenMsg = tokenList.get(0);

            token = getToken(tokenMsg);

        }

        return token;
    }

    /**
     * 把json转换成企业信息
     *
     * @author Huanglongjian
     * @date 2019-07-22 15:18
     */
    private static Enterprise jsonObjToEnt(JSONObject jsonObjectEnt) {
        Enterprise enterprise = new Enterprise();
        try {
            enterprise.setApiStrictSecurityFlag(jsonObjectEnt.getString("apiStrictSecurityFlag"));
            enterprise.setAppStrictSecurityFlag(jsonObjectEnt.getString("appStrictSecurityFlag"));
            enterprise.setCorpid(jsonObjectEnt.getLong("corpid"));
            enterprise.setId(jsonObjectEnt.getLong("id"));
            enterprise.setLogin(jsonObjectEnt.getString("login"));
            enterprise.setOrgId(jsonObjectEnt.getLong("orgId"));
            enterprise.setStrictSecurityFlag(jsonObjectEnt.getString("strictSecurityFlag"));
            enterprise.setCheckScopeFlag(jsonObjectEnt.getString("checkScopeFlag"));
            enterprise.setIndustryVersion(jsonObjectEnt.getString("industryVersion"));
            enterprise.setIsMultiPriceList(jsonObjectEnt.getString("isMultiPriceList"));
            enterprise.setvAcctId(jsonObjectEnt.getLong("vAcctId"));
            enterprise.setvAcctName("vAcctName");
            enterprise.setUserName(jsonObjectEnt.getString("userName"));
        } catch (Exception e) {
            logger.error(e.getMessage());
            throw e;
        }
        return enterprise;
    }

    /**
     * 把json转换成用户信息
     *
     * @author Huanglongjian
     * @date 2019-07-22 15:18
     */
    private static User jsonObjToUser(JSONObject jsonObjectUser, String clientId) throws ExecutionException {
        User user = new User();
        user.setCorpid(jsonObjectUser.getLong("corpid"));
        user.setDutyId(jsonObjectUser.getLong("dutyId"));
        user.setUsername(jsonObjectUser.getString("username"));
        user.setCurClientId(jsonObjectUser.getString("curClientId"));
        user.setFirstName(jsonObjectUser.getString("firstName"));
        user.setId(jsonObjectUser.getLong("id"));
        user.setOrgId(jsonObjectUser.getLong("orgId"));
        user.setPostnId(jsonObjectUser.getLong("postnId"));
        user.setAcctId(jsonObjectUser.getLong("acctId"));
        user.setLoginType(jsonObjectUser.getString("loginType"));
        List<Long> idList = null;
        if ("api".equalsIgnoreCase(RunProperties.SCOPE.get())) {
            idList = CacheUtil.getClientDutyCache(clientId);
        } else {
            idList = JSONObject.parseArray(
                    jsonObjectUser.getJSONArray("dutyIdList").toJSONString(), Long.class);

        }
        user.setDutyIdList(idList);
        user.setDynamicAeskey(jsonObjectUser.getString("dynamicAeskey"));
        return user;
    }

    /**
     * 获取当前token里面的用户
     *
     * @author huanglongjian
     * @date 2019年07月23日
     */
    public static User getTokenUser() {
        return  RunProperties.TEMP_USER.get();
    }

    /**
     * 校验当前请求是否是不拦截的
     *
     * @author huanglongjian
     * @date 2019年07月23日
     */
    public static Boolean checkNoIntercept(ServerHttpRequest request) {
        //获取header信息
        List<String> headers = request.getHeaders().get(TokenConstants.NO_INTERCEPT_FLAG_NAME);
        if (headers == null || headers.size() < 1) {
            //如果header没有值。则表示当前不是不拦截请求
            return false;
        }
        String headerValue = headers.get(0);
        //校验header的值
        return TokenConstants.NO_INTERCEPT_FLAG_VALUE.equals(headerValue);
    }

    /**
     * 请求走完之后，比如删除掉线程中的数据
     *
     * @author huanglongjian
     * @date 2019年07月23日
     */
    public static void postHandle() {
        RunProperties.TEMP_USER.remove();
        RunProperties.TEMP_ENTERPRISE.remove();
        RunProperties.TOKEN.remove();
        RunProperties.CURRENT_CLIENT_ID.remove();
        RunProperties.SCOPE.remove();
    }

}
